#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <map>
#include <stdlib.h>
#include <lineages.h>
#include <algorithm>
#include <stdio.h>
#include <sstream>
#include <math.h>
#include <time.h>
#include <shared.h>

using namespace std;

int fitnessType;
int robustType;
int disturbType;

double baseDelRate = 1.0;

void usage()
{
	cerr << "Generate a set of random lineages that is based on the a real lineage" << endl;
	cerr << "Usage: mkRandom func lin num seed collect" << endl;
	cerr << "Example: mkRandom 1 fun.alm 100 123 fun.random.lineages" << endl;
	cerr << "Note: The H.roretzi lineage is handled differently, see \"mirrorRandom\" instead" << endl;
	cerr << "Parameters (all are needed) :"<< endl;
	cerr << " func      : The method of generating the random lineages" << endl;
	cerr << "             1. total random coalescence" << endl;
	cerr << "             2. keep the shape of the lineage tree, but randomly shuffle the terminal cells" << endl;
	cerr << "             3. generate \"num\" simulated trees for each of the maximum depths in M to (2M + 2 - X) ," << endl;
	cerr << "                where X is the maximum depth of the real lineage," << endl;
	cerr << "                and M is the theoretically lowest possible maximum depth of the real lineage." << endl;
	cerr << "             4. generate random lineages so that the collection of terminal depths in each of them" << endl;
	cerr << "                is exactly the same as the real lineage" << endl;
	cerr << "             5. generate random lineages so that the collection of terminal depths in each of them" << endl;
	cerr << "                is a bootstrap sample of the collection of terminal depths in the real lineage" << endl;
	cerr << "             6. shuffle the terminal cells within their own depth, do not change lineage tree shape" << endl;
	cerr << "             7. shuffle the terminal cells within their own depth, but make \"twins\" remained \"twins\" " << endl;
	cerr << "                here \"twins\" means sister cells of the same type" << endl;
	cerr << "             8. shuffle the terminal cells within their own depth, "<< endl;
	cerr << "                and made P% of cells in the same type as clustered as possible." << endl;
	cerr << "                Here P% uses every 5th percentile, thus there are 21 * \"num\" simulated lineages." << endl;
	cerr << "             9. swap 5% of cells out of their depth and then shuffle terminal cells within their own depth" << endl;
	cerr << " lin       : Name of a lineage file (one cell per line)" << endl;
	cerr << "             The random lineages are generated based on this lineage." << endl;
	cerr << "             The program use a silly match-by-string way to recognize what lineages are being used." << endl;
	cerr << "             The matches are as follows:" << endl;
	cerr << "             \"fun\".  C. elegans lineage and functional classification" << endl;
	cerr << "             \"pma\".  P. marina lineage and functional classification" << endl;
	cerr << "             \"hro\".  H. roretzi lineage and functional classification" << endl;
	cerr << "             \"cel\".  C. elegans lineage and functional classification, with neuron subtypes defined" << endl;
	cerr << "             \"cpe\".  C. elegans lineage up to the post-embryonic stage, and functional classification" << endl;
	cerr << "             So your filename must contain one of these five strings, otherwise the program will fail" << endl;
	cerr << "             The program need to know what lineage is being used because" << endl;
	cerr << "             it need to \"understand\" the names of functional classification" << endl;
	cerr << "             I recommend only those lineage files downloadable from the program's google code project site." << endl;
	cerr << " num       : Generate how many simulated lineages." << endl;
	cerr << "             Note that for \"func\" = 3 or 8, there will be a multiplication of \"num\" lineages." << endl;
	cerr << " seed      : Seed for randomization" << endl;
	cerr << " collect   : Filename for the lineage collection (one lineage per line)" << endl;
	cerr << "             It must follow the same rule as \"lin\"" << endl;
	cerr << " " << endl;
	return;
}

int main(int argc, char *argv[])
{
    if(argc != 6)
	{
		usage();
		exit(255);
	}
    int func = atoi(argv[1]);
    string realLineagesFile = argv[2];
    int simLoop = atoi(argv[3]);
    int randSeed = atoi(argv[4]);
    string outputFile = argv[5];
    srand(randSeed);

    vector<int> termTypes;
    vector<string> termLineages;

    loadRealLineages(realLineagesFile,termTypes,termLineages);
    Lineages realLin(termTypes,termLineages);
    realLin.getDepthStat();

    int outputBuffer = 100;
    vector<vector<int> > typesPool;
    vector<vector<string> > lineagesPool;
    if(func == 1) // random topology
    {
        for(int i=0; i<simLoop; i++)
        {
            vector<string> rndTopo;
            generateRandomLineageTopology(termTypes.size(),rndTopo);
            typesPool.push_back(termTypes);
            lineagesPool.push_back(rndTopo);
            if(i == 0)
            {
                writeLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
            else if((i % outputBuffer == 0) or (i == simLoop - 1) )
            {
                appendLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
        }
    }
    else if(func == 2) // random order of terminal cells
    {
        for(int i=0; i<simLoop; i++)
        {
            vector<int> randTypes = termTypes;
            random_shuffle(randTypes.begin(),randTypes.end());
            typesPool.push_back(randTypes);
            lineagesPool.push_back(termLineages);

            if(i == 0)
            {
                writeLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
            else if((i % outputBuffer == 0) or (i == simLoop - 1) )
            {
                appendLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
        }
    }
    else if(func == 3) // random topology with controlled depth limit
    {
        int minDepth,maxDepth;
        minDepth = (int)(log(termTypes.size()) / log(2)) + 1;
        maxDepth = (abs(minDepth - (int)realLin.depthMax) + 1 ) * 2 + (int)realLin.depthMax;
        //minDepth = realLin.depthMax;
        //maxDepth = realLin.depthMax;
        for(int j=minDepth; j <= maxDepth; j++)
        {
            for(int i=0; i<simLoop; i++)
            {
                vector<string> rndTopo;
                generateRandomLineageTopologyLimitDepth(termTypes.size(),j,rndTopo);

                typesPool.push_back(termTypes);
                lineagesPool.push_back(rndTopo);
                if((i == 0) and (j == minDepth))
                {
                    writeLineageCollection(outputFile,typesPool,lineagesPool);
                    typesPool.clear();
                    lineagesPool.clear();
                }
                else if((i % outputBuffer == 0)  or (i == simLoop - 1) )
                {
                    appendLineageCollection(outputFile,typesPool,lineagesPool);
                    typesPool.clear();
                    lineagesPool.clear();
                }
            }
        }
    }
    else if(func == 4) // random topology with same depths of all terminal cells
    {
        vector<string> randLineages;
        vector<int> termDepths;
        for(int i=0; i<simLoop; i++)
        {

            termDepths.clear();
            for(unsigned int j=0;j<termLineages.size();j++)
            {
                termDepths.push_back(termLineages[j].length());
            }
            random_shuffle(termDepths.begin(),termDepths.end());
            depthFixedCoalescent2(termDepths,randLineages);

            typesPool.push_back(termTypes);
            lineagesPool.push_back(randLineages);
            if(i == 0)
            {
                writeLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
            else if( (i % outputBuffer == 0) or (i == simLoop - 1) )
            {
                appendLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
        }
    }
    else if(func == 5) // randomly sample real depths then build tree for them
    {
    	vector<int> sampledDepths;
    	vector<string> randLineages;
		for(int i=0; i<simLoop; i++)
        {
        	sampledDepths.clear();
        	randLineages.clear();
			do{
			    sampledDepths.clear();
				for(unsigned int j=0;j<termLineages.size();j++)
				{
					sampledDepths.push_back(termLineages[(int)random(0.0,(double)termLineages.size())].length());
				}
			} while(!goodDepthSet(sampledDepths));

            depthFixedCoalescent2(sampledDepths,randLineages);

            typesPool.push_back(termTypes);
            lineagesPool.push_back(randLineages);
            if(i == 0)
            {
                writeLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
            else if( (i % outputBuffer == 0) or (i == simLoop - 1) )
            {
                appendLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
        }

    }
    else if(func == 6) // shuffled termini within their depth
    {
        for(int i=0; i<simLoop; i++)
        {
            vector<string> rndLin;
            typesPool.push_back(termTypes);
            depthFixedShuffle(termLineages,rndLin);
            lineagesPool.push_back(rndLin);

            if(i == 0)
            {
                writeLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
            else if((i % outputBuffer == 0) or (i == simLoop - 1) )
            {
                appendLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
        }
    }
    else if(func == 7) // shuffle within depth, maintaining twins
    {
        for(int i=0; i<simLoop; i++)
        {
            vector<string> rndLin;
            typesPool.push_back(termTypes);
            depthFixedShuffle_twin(termLineages,termTypes,rndLin); // twin means instant siblings of the same type
            lineagesPool.push_back(rndLin);

            if(i == 0)
            {
                writeLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
            else if((i % outputBuffer == 0) or (i == simLoop - 1) )
            {
                appendLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
        }
    }
    else if(func == 8) // shuffle within depth, made given types as cluster as possible
    {
        map<int,int> allTypes;
        map<int,int>::iterator typesIt;
        int poolCnt = 0;
        for(unsigned int i=0;i<termTypes.size();i++)
        {
            allTypes[termTypes[i]] = 1;
        }

		for(int i=0; i<=100; i+=5)
		{
			for(int j=0; j<simLoop; j++)
			{
				vector<string> rndLin;
				vector<int> rndTypes;
				depthFixedAllClusterShuffle(termLineages,termTypes,((double)i) / 100.0,rndLin,rndTypes);
				typesPool.push_back(rndTypes);
				lineagesPool.push_back(rndLin);

				if(poolCnt == 0)
				{
					writeLineageCollection(outputFile,typesPool,lineagesPool);
					typesPool.clear();
					lineagesPool.clear();
				}
				else if(poolCnt % outputBuffer == 0)
				{
					appendLineageCollection(outputFile,typesPool,lineagesPool);
					typesPool.clear();
					lineagesPool.clear();
				}
				poolCnt ++;
			}
		}
        if(typesPool.size() > 0);
        {
            appendLineageCollection(outputFile,typesPool,lineagesPool);
            typesPool.clear();
            lineagesPool.clear();
        }
    }
    else if(func == 10) // scratch
    {
        map<int,int> allTypes;
        map<int,int>::iterator typesIt;
        int poolCnt = 0;
        for(unsigned int i=0;i<termTypes.size();i++)
        {
            allTypes[termTypes[i]] = 1;
        }

        for(typesIt=allTypes.begin();typesIt!=allTypes.end();typesIt++)
        {
            if(typesIt->first == Lineages::sparableCellType) continue;
            //if(types2Desc(typesIt->first, realLineagesFile) == "und" ) continue; // don't try to cluster the undifferentiated cells
            for(int i=0; i<=100; i+=5)
            {
                for(int j=0; j<simLoop; j++)
                {
                    vector<string> rndLin;
                    vector<int> rndTypes;
                    depthFixedClusterShuffle(termLineages,termTypes,typesIt->first,((double)i) / 100.0,rndLin,rndTypes);
                    typesPool.push_back(rndTypes);
                    lineagesPool.push_back(rndLin);

                    if(poolCnt == 0)
                    {
                        writeLineageCollection(outputFile,typesPool,lineagesPool);
                        typesPool.clear();
                        lineagesPool.clear();
                    }
                    else if(poolCnt % outputBuffer == 0)
                    {
                        appendLineageCollection(outputFile,typesPool,lineagesPool);
                        typesPool.clear();
                        lineagesPool.clear();
                    }
                    poolCnt ++;
                }
            }
        }
        if(typesPool.size() > 0);
        {
            appendLineageCollection(outputFile,typesPool,lineagesPool);
            typesPool.clear();
            lineagesPool.clear();
        }
    }
	else if(func == 9) // swap a fraction of cells out of their depth, and then shuffle within their depth
	{
		for(int i=0; i<simLoop; i++)
        {
            vector<string> rndLin;
            typesPool.push_back(termTypes);
            depthSemifixedShuffle(termLineages,rndLin,0.05); // even 5% out of depth still makes the lineage have weaker rare-early correlation
            lineagesPool.push_back(rndLin);

            if(i == 0)
            {
                writeLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
            else if((i % outputBuffer == 0) or (i == simLoop - 1) )
            {
                appendLineageCollection(outputFile,typesPool,lineagesPool);
                typesPool.clear();
                lineagesPool.clear();
            }
        }
	}

    return 0;
}
